"""
Async HTTP client following SOLID principles.
"""

import time
import json
import asyncio
from typing import Dict, Any, Optional, Union
import httpx
from .models import HTTPRequest, HTTPResponse, HTTPClientError, HTTPTimeoutError, HTTPConnectionError
from ..logging import get_logger


class AsyncHTTPClient:
    """Async HTTP client with retry and logging capabilities."""
    
    def __init__(
        self,
        base_url: Optional[str] = None,
        timeout: float = 30.0,
        headers: Optional[Dict[str, str]] = None,
        retry_attempts: int = 3,
        retry_delay: float = 1.0
    ):
        self.base_url = base_url
        self.timeout = timeout
        self.default_headers = headers or {}
        self.retry_attempts = retry_attempts
        self.retry_delay = retry_delay
        self.logger = get_logger("http_client")
        self._client: Optional[httpx.AsyncClient] = None
    
    async def __aenter__(self):
        """Async context manager entry."""
        await self._ensure_client()
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Async context manager exit."""
        await self.close()
    
    async def _ensure_client(self):
        """Ensure HTTP client is initialized."""
        if self._client is None:
            self._client = httpx.AsyncClient(
                base_url=self.base_url,
                timeout=self.timeout,
                headers=self.default_headers
            )
    
    async def close(self):
        """Close HTTP client."""
        if self._client:
            await self._client.aclose()
            self._client = None
    
    async def _make_request(
        self,
        method: str,
        url: str,
        headers: Optional[Dict[str, str]] = None,
        params: Optional[Dict[str, Any]] = None,
        json_data: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        timeout: Optional[float] = None
    ) -> HTTPResponse:
        """Make HTTP request with retry logic."""
        await self._ensure_client()
        
        request_headers = {**self.default_headers}
        if headers:
            request_headers.update(headers)
        
        request_timeout = timeout or self.timeout
        
        for attempt in range(self.retry_attempts):
            try:
                start_time = time.time()
                
                response = await self._client.request(
                    method=method,
                    url=url,
                    headers=request_headers,
                    params=params,
                    json=json_data,
                    data=data,
                    timeout=request_timeout
                )
                
                elapsed = time.time() - start_time
                
                # Parse JSON if possible
                json_response = None
                try:
                    json_response = response.json()
                except (json.JSONDecodeError, ValueError):
                    pass
                
                http_response = HTTPResponse(
                    status_code=response.status_code,
                    headers=dict(response.headers),
                    content=response.content,
                    text=response.text,
                    json_data=json_response,
                    url=str(response.url),
                    elapsed=elapsed
                )
                
                # Log request
                self.logger.info(
                    "HTTP Request",
                    method=method,
                    url=url,
                    status_code=response.status_code,
                    duration_ms=round(elapsed * 1000, 2),
                    attempt=attempt + 1
                )
                
                # Raise for HTTP errors
                response.raise_for_status()
                
                return http_response
                
            except httpx.TimeoutException as e:
                if attempt == self.retry_attempts - 1:
                    self.logger.error("HTTP Timeout", url=url, attempt=attempt + 1)
                    raise HTTPTimeoutError(f"Request timeout: {url}") from e
                
            except httpx.ConnectError as e:
                if attempt == self.retry_attempts - 1:
                    self.logger.error("HTTP Connection Error", url=url, attempt=attempt + 1)
                    raise HTTPConnectionError(f"Connection error: {url}") from e
                
            except httpx.HTTPStatusError as e:
                self.logger.error(
                    "HTTP Status Error",
                    url=url,
                    status_code=e.response.status_code,
                    attempt=attempt + 1
                )
                raise HTTPClientError(
                    f"HTTP {e.response.status_code}: {url}",
                    response=HTTPResponse(
                        status_code=e.response.status_code,
                        headers=dict(e.response.headers),
                        content=e.response.content,
                        text=e.response.text,
                        json_data=None,
                        url=str(e.response.url),
                        elapsed=0
                    )
                ) from e
            
            # Wait before retry
            if attempt < self.retry_attempts - 1:
                await asyncio.sleep(self.retry_delay * (2 ** attempt))
    
    async def get(
        self,
        url: str,
        params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
        timeout: Optional[float] = None
    ) -> HTTPResponse:
        """Make GET request."""
        return await self._make_request("GET", url, headers, params, timeout=timeout)
    
    async def post(
        self,
        url: str,
        json_data: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        headers: Optional[Dict[str, str]] = None,
        timeout: Optional[float] = None
    ) -> HTTPResponse:
        """Make POST request."""
        return await self._make_request("POST", url, headers, json_data=json_data, data=data, timeout=timeout)
    
    async def put(
        self,
        url: str,
        json_data: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        headers: Optional[Dict[str, str]] = None,
        timeout: Optional[float] = None
    ) -> HTTPResponse:
        """Make PUT request."""
        return await self._make_request("PUT", url, headers, json_data=json_data, data=data, timeout=timeout)
    
    async def delete(
        self,
        url: str,
        headers: Optional[Dict[str, str]] = None,
        timeout: Optional[float] = None
    ) -> HTTPResponse:
        """Make DELETE request."""
        return await self._make_request("DELETE", url, headers, timeout=timeout)
    
    async def patch(
        self,
        url: str,
        json_data: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        headers: Optional[Dict[str, str]] = None,
        timeout: Optional[float] = None
    ) -> HTTPResponse:
        """Make PATCH request."""
        return await self._make_request("PATCH", url, headers, json_data=json_data, data=data, timeout=timeout)